package org.ecpkn.PAGridSim.core.gridsimEntity.policy.paPolicy.preemptable;

import gridsim.GridSim;
import gridsim.Gridlet;
import gridsim.PE;
import gridsim.PEList;
import gridsim.ResGridlet;
import gridsim.ResGridletList;

import java.util.Calendar;
import java.util.Collections;

import org.ecpkn.PAGridSim.core.gridsimEntity.paMachine.PAMachine;
import org.ecpkn.PAGridSim.core.tasksCreation.Job;

/**
 * This class extend PreemptablePAPolicy class and implements Global allocation
 * policy
 * 
 */

public abstract class GlobalPreemptablePAPolicy extends PreemptablePAPolicy {

	/**
	 * Allocates a new GlobalPreemptablePAPolicy object
	 * 
	 * @param resourceName
	 *            GridResource name that will contain this allocation policy
	 * @param entityName
	 *            this policy entity name
	 * @throws Exception
	 *             This happens when one of the following scenarios occur:
	 *             creating this entity before initializing GridSim package;
	 *             this entity name is null or empty;this entity has zero number
	 *             of PEs (Processing Elements). No PEs mean the Gridlets can't
	 *             be processed. A GridResource must contain one or more
	 *             Machines. A Machine must contain one or more PEs.
	 * @see gridsim.GridSim#init(int, Calendar, boolean, String[], String[],
	 *      String)
	 * @pre resourceName != null;entityName != null
	 */
	public GlobalPreemptablePAPolicy(String resourceName, String policyName)
			throws Exception {
		super(resourceName, policyName);
	}

	protected void schedule(ResGridlet rgl) {
		// if there is an available machine, then allocate immediately
		if (gridletInExecList_.size() < getAwakePAMachineList().size()) {
			// it should absolutely succeed
			allocatePEtoGridlet(rgl, this.resource_.getMachineWithFreePE()
					.getMachineID());
		} else {
			// if there is no available machine, then decide whether to
			// preempt and which one to preempt
			tryToPreempt(rgl, gridletInExecList_);
		}
	}

	/**
	 * @param rgl
	 * @param taskMachineID
	 * @param freeMachineID
	 * @return
	 */
	protected boolean allocatePEtoGridlet(ResGridlet rgl, int taskMachineID,
			int freeMachineID) {
		return allocatePEtoGridlet(rgl, freeMachineID);
	}

	/**
	 * Allocates the first Gridlet in the Queue list (if any) to execution list
	 */
	protected void allocateQueueAndPauseGridlet(int freeMachineID) {
		// if there are many Gridlets in the QUEUE, then allocate a
		// PE to the first Gridlet in the list since it follows FCFS
		// (First Come First Serve) approach. Then removes the Gridlet from
		// the Queue list
		if (freeMachineID >= this.getAwakePAMachineList().size()) {
			// the machine may have to be show down in the new sample period, do
			// nothing in this case
			return;
		}
		Job j = null;

		ResGridlet obj = null;
		// queue and paused list
		ResGridletList qpList = new ResGridletList();
		// compare the queue list and the paused list at the same time
		qpList.addAll(gridletQueueList_);
		qpList.addAll(gridletPausedList_);

		Collections.sort(qpList, getPriorityComparator());
		boolean success = false;
		int i = 0;
		if ((qpList.size() > 0)) {
			while (j == null && i < qpList.size()) {
				j = (Job) qpList.get(i).getGridlet();
				obj = (ResGridlet) qpList.get(i);
				// allocate the Gridlet into an empty PE slot and remove it
				// from the queue list

				if (gridletQueueList_.contains(obj)) {
					success = allocatePEtoGridlet(obj, freeMachineID);
					if (success == true) {
						gridletQueueList_.remove(obj);
						break;
					}
					else {
						j = null;
						i++;
					}
				} else {
					success = resumeGridletFromPause(obj, freeMachineID);
					if (success == true) {
						gridletPausedList_.remove(obj);
						break;
					} else {
						j = null;
						i++;
					}
				}
			}
		}
	}

	/**
	 * Allocates a Gridlet into a free PE and sets the Gridlet status into
	 * INEXEC and PE status into busy afterwards
	 * 
	 * @param rgl
	 *            a ResGridlet object
	 * @param freeMachineID
	 * @return <tt>true</tt> if there is an empty PE to process this Gridlet,
	 *         <tt>false</tt> otherwise
	 * @pre rgl != null
	 */
	protected boolean allocatePEtoGridlet(ResGridlet rgl, int freeMachineID) {
		PAMachine myMachine = getMachineForGridlet(freeMachineID);
		// If no Machine is empty then ignore the rest
		if (myMachine == null) {
			return false;
		}
		// gets the list of PEs and find one empty PE
		PEList MyPEList = myMachine.getPEList();
		int freePE = MyPEList.getFreePEID();
		// Identify Completion Time and Set Failed if necessary
		int rating = myMachine.getMIPSRating();
		double execuTime = forecastFinishTime(rating,
				rgl.getRemainingGridletLength());
		// we need a rounding up here, because if we send the finish signal too
		// early, and actually the job has not yet been finished,it will lost
		// the first chance of finish check and it has to wait for the next one
		// to give a signal.In some cases,the next one come too late,which can
		// cause it to lose its deadline
		double roundUpTime = execuTime + GridSim.clock() + 0.001;

		// should we allow a job to continue when we are sure that it will fail
		// if (job.getDeadline_() < roundUpTime) {
		// rgl.setGridletStatus(Gridlet.FAILED);
		// return false;
		// }
		// ALLOCATE IMMEDIATELY
		rgl.setGridletStatus(Gridlet.INEXEC); // change Gridlet status
		rgl.setMachineAndPEID(myMachine.getMachineID(), freePE);
		// add this Gridlet into execution list
		gridletInExecList_.add(rgl);
		// Set allocated PE to BUSY status
		super.resource_.setStatusPE(PE.BUSY, rgl.getMachineID(), freePE);
		rgl.setFinishTime(roundUpTime);
		// then send this into itself
		super.sendInternalEvent(roundUpTime - GridSim.clock());
		return true;
	}

	/**
	 * 
	 * @param taskMachineID
	 * @param freeMachineID
	 * @param job
	 * @return
	 */
	private PAMachine getMachineForGridlet(int freeMachineID) {
		return this.getAwakePAMachineList().getMachine(freeMachineID);
	}
}
